package org.imperativetable;

import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.imperativetable.constants.Position;
import org.imperativetable.i18n.MessageResources;

public class Table<E> {

    /*
     * Main table properties
     */
    private Collection<E> data;
    private List<Column> columns = new ArrayList<Column>();
    /*
     * Column index to sort table by, starting from 0, -1 means do not sort
     */
    private int sortColumn = -1;
    /*
     * Column sort order: 1 means ascending, -1 means descending
     */
    private int sortOrder = 1;
    /*
     * Current table page, 0 means fist page
     */
    private int currentPage = 0;
    /*
     * Row count to be displayed on page
     */
    private int pageSize = -1;
    /*
     * Maximum number of paging links to be displayed, -1 means no restriction
     */
    private int maxPagingLinkCount = -1;

    /*
     * HTML class atributes
     */
    private Set<String> tableClasses = new HashSet<String>();
    private Set<String> headerClasses = new HashSet<String>();
    private Set<String> cellClasses = new HashSet<String>();
    private Set<String> sortedColumnClasses = new HashSet<String>();
    private Set<String> evenRowClasses = new HashSet<String>();
    private Set<String> oddRowClasses = new HashSet<String>();
    private Set<String> pagingClasses = new HashSet<String>();
    private Set<String> currentPageClasses = new HashSet<String>();
    /*
     * HTML style attributes
     */
    private String tableStyle;
    private String headerStyle;
    private String cellStyle;
    private String sortedColumnStyle;
    private String evenRowStyle;
    private String oddRowStyle;
    private String pagingStyle;
    private String currentPageStyle;
    /*
     * Paging properties
     */
    private String firstPageLinkValueContent;
    private String previousPageLinkContent;
    private String nextPageLinkContent;
    private String lastPageLinkContent;
    /*
     * Variable stores formats in which table can be exported.
     * If exportLinkContent properties is not null, content of this
     * variable will be ignored.
     */
    private Set<Integer> exportFormats = new HashSet<Integer>();
    /*
     * This variable represents the format in which export links will be
     * outputted. Use identifiers starting with $ to describe link href to
     * desired format, for example:
     * $csv - for csv,
     * $xml - for xml.
     * Also this identifiers are stored in Export class:
     * Export.CSV_LINK = "$csv"
     * EXPORT.XML_LINK = "$xml"
     */
    private String exportLinkContent;
    /*
     * Table caption which will be displayed before table
     */
    private String caption;
    /*
     * Table footer, which will be displayed after paging
     */
    private String footer;
    /*
     * Vertical positions of paging and export
     */
    private Integer pagingVerticalPosition = Position.BOTTOM;
    private Integer exportVerticalPosition = Position.TOP;
    /*
     * Message resources
     */
    private MessageResources messageResources;

    /*
     * Main table methods
     */
    public void addColumn(Column column) {
        columns.add(column);
    }

    public void addDataRow(E rowObject) {
        if (data == null) {
            data = new ArrayList<E>();
        }
        data.add(rowObject);
    }

    public Collection<E> getData() {
        return data;
    }

    public void setData(Collection<E> data) {
        this.data = data;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public int getMaxPagingLinkCount() {
        return maxPagingLinkCount;
    }

    public void setMaxPagingLinkCount(int maxPagingLinkCount) {
        this.maxPagingLinkCount = maxPagingLinkCount;
    }

    public int getSortedColumn() {
        return sortColumn;
    }

    public void setSortedColumn(int sortedColumn) {
        this.sortColumn = sortedColumn;
    }

    public void setDefaultSortedColumn(int defaultSortedColumn) {
        this.sortColumn = defaultSortedColumn;
    }

    public boolean isSortOrderAscending() {
        return sortOrder > 0;
    }

    public void setSortOrderAscending() {
        sortOrder = 1;
    }

    public boolean isSortOrderDescending() {
        return sortOrder < 0;
    }

    public void setSortOrderDescending() {
        sortOrder = -1;
    }

    public Object[] getHeaders() {
        Object[] headers = new Object[columns.size()];
        for (int i = 0; i < columns.size(); i++) {
            headers[i] = columns.get(i).getTitle();
        }
        return headers;
    }

    /*
     * Main methods for rendering - returns list of rows, paging, etc
     * depending on current state of table:
     * currentPage,
     * sortedColumn,
     * sortedOrder,
     * etc.
     */
    public List<Object[]> getCurrentTablePage() {
        List<Object[]> rows = new ArrayList<Object[]>();

        /*
         * Fill table rows
         */
        for (E element : data) {
            Object[] row = new Object[columns.size()];
            for (int i = 0; i < columns.size(); i++) {
                row[i] = columns.get(i).getColumnContent().getValue(element, messageResources);
            }
            rows.add(row);
        }

        /*
         * Sort table data
         */
        if (sortColumn >= 0) {
            Comparator<Object[]> comparator = new Comparator<Object[]>() {

                public int compare(Object[] o1, Object[] o2) {
                    if (columns.get(sortColumn).getComparator() != null) {
                        return sortOrder * columns.get(sortColumn).getComparator().compare(o1[sortColumn], o2[sortColumn]);
                    }
                    if (o1[sortColumn] instanceof Comparable) {
                        return sortOrder * ((Comparable) o1[sortColumn]).compareTo(o2[sortColumn]);
                    } else {
                        return sortOrder * o1[sortColumn].toString().compareTo(o2[sortColumn].toString());
                    }
                }
            };

            Collections.sort(rows, comparator);
        }

        /*
         * Cut data to current page
         */
        if (pageSize >= 0) {
            return rows.subList(currentPage * pageSize, (currentPage + 1) * pageSize);
        } else {
            return rows;
        }
    }

    /*
     * Render method related to Renderer class with static method render
     */
    public void render(HttpServletRequest request, Writer writer) {
        Renderer.render(request, writer, this);
    }

    /*
     * Accessors for HTML class attributes
     */
    public void addTableClass(String className) {
        tableClasses.add(className);
    }

    public Set<String> getTableClasses() {
        return tableClasses;
    }

    public void setTableClasses(Set<String> tableClasses) {
        this.tableClasses = tableClasses;
    }

    public void addHeaderClass(String className) {
        headerClasses.add(className);
    }

    public Set<String> getHeaderClasses() {
        return headerClasses;
    }

    public void setHeaderClasses(Set<String> headerClasses) {
        this.headerClasses = headerClasses;
    }

    public void addCellClass(String className) {
        cellClasses.add(className);
    }

    public Set<String> getCellClasses() {
        return cellClasses;
    }

    public void setCellClasses(Set<String> cellClasses) {
        this.cellClasses = cellClasses;
    }

    public void addSortedColumnClass(String className) {
        sortedColumnClasses.add(className);
    }

    public Set<String> getSortedColumnClasses() {
        return sortedColumnClasses;
    }

    public void setSortedColumnClasses(Set<String> sortedColumnClasses) {
        this.sortedColumnClasses = sortedColumnClasses;
    }

    public void addEvenRowClass(String className) {
        evenRowClasses.add(className);
    }

    public Set<String> getEvenRowClasses() {
        return evenRowClasses;
    }

    public void setEvenRowClasses(Set<String> evenRowClasses) {
        this.evenRowClasses = evenRowClasses;
    }

    public void addOddRowClass(String className) {
        oddRowClasses.add(className);
    }

    public Set<String> getOddRowClasses() {
        return oddRowClasses;
    }

    public void setOddRowClasses(Set<String> oddRowClasses) {
        this.oddRowClasses = oddRowClasses;
    }

    public void addPagingClass(String className) {
        pagingClasses.add(className);
    }

    public Set<String> getPagingClasses() {
        return pagingClasses;
    }

    public void setPagingClasses(Set<String> pagingClasses) {
        this.pagingClasses = pagingClasses;
    }

    public void addCurrentPageClass(String className) {
        currentPageClasses.add(className);
    }

    public Set<String> getCurrentPageClasses() {
        return currentPageClasses;
    }

    public void setCurrentPageClasses(Set<String> currentPageClasses) {
        this.currentPageClasses = currentPageClasses;
    }

    /*
     * Accessors for HTML style attributes
     */
    public String getTableStyle() {
        return tableStyle;
    }

    public void setTableStyle(String tableStyle) {
        this.tableStyle = tableStyle;
    }

    public String getHeaderStyle() {
        return headerStyle;
    }

    public void setHeaderStyle(String headerStyle) {
        this.headerStyle = headerStyle;
    }

    public String getCellStyle() {
        return cellStyle;
    }

    public void setCellStyle(String cellStyle) {
        this.cellStyle = cellStyle;
    }

    public String getSortedColumnStyle() {
        return sortedColumnStyle;
    }

    public void setSortedColumnStyle(String sortedColumnStyle) {
        this.sortedColumnStyle = sortedColumnStyle;
    }

    public String getEvenRowStyle() {
        return evenRowStyle;
    }

    public void setEvenRowStyle(String evenRowStyle) {
        this.evenRowStyle = evenRowStyle;
    }

    public String getOddRowStyle() {
        return oddRowStyle;
    }

    public void setOddRowStyle(String oddRowStyle) {
        this.oddRowStyle = oddRowStyle;
    }

    public String getPagingStyle() {
        return pagingStyle;
    }

    public void setPagingStyle(String pagingStyle) {
        this.pagingStyle = pagingStyle;
    }

    public String getCurrentPageStyle() {
        return currentPageStyle;
    }

    public void setCurrentPageStyle(String currentPageStyle) {
        this.currentPageStyle = currentPageStyle;
    }

    /*
     * Setting paging links
     */
    public String getFirstPageLinkValueContent() {
        return firstPageLinkValueContent;
    }

    public void setFirstPageLinkValueContent(String firstPageLinkValueContent) {
        this.firstPageLinkValueContent = firstPageLinkValueContent;
    }

    public String getPreviousPageLinkContent() {
        return previousPageLinkContent;
    }

    public void setPreviousPageLinkContent(String previousPageLinkContent) {
        this.previousPageLinkContent = previousPageLinkContent;
    }

    public String getNextPageLinkContent() {
        return nextPageLinkContent;
    }

    public void setNextPageLinkContent(String nextPageLinkContent) {
        this.nextPageLinkContent = nextPageLinkContent;
    }

    public String getLastPageLinkContent() {
        return lastPageLinkContent;
    }

    public void setLastPageLinkContent(String lastPageLinkContent) {
        this.lastPageLinkContent = lastPageLinkContent;
    }

    public Integer getPagingVerticalPosition() {
        return pagingVerticalPosition;
    }

    public void setPagingVerticalPosition(Integer pagingVerticalPosition) {
        this.pagingVerticalPosition = pagingVerticalPosition;
    }

    /*
     * Setting export properties
     */
    public Set<Integer> getExportFormats() {
        return exportFormats;
    }

    public void setExportFormats(Set<Integer> exportFormats) {
        this.exportFormats = exportFormats;
    }

    public String getExportLinkContent() {
        return exportLinkContent;
    }

    public void setExportLinkContent(String exportLinkContent) {
        this.exportLinkContent = exportLinkContent;
    }

    public Integer getExportVerticalPosition() {
        return exportVerticalPosition;
    }

    public void setExportVerticalPosition(Integer exportVerticalPosition) {
        this.exportVerticalPosition = exportVerticalPosition;
    }

    /*
     * Accessors for caption and footer
     */
    public String getCaption() {
        return caption;
    }

    public void setCaption(String caption) {
        this.caption = caption;
    }

    public String getFooter() {
        return footer;
    }

    public void setFooter(String footer) {
        this.footer = footer;
    }

    /*
     * Message resources
     */
    public MessageResources getMessageResources() {
        return messageResources;
    }

    public void setMessageResources(MessageResources messageResources) {
        this.messageResources = messageResources;
    }
}
